---
title: Zero Copy
description:
  Learn how to use Anchor's zero-copy deserialization feature to handle large
  account data in Solana programs.
---

## Usage

Zero copy is a deserialization feature that allows programs to read account data
directly from memory without copying it. This is particularly useful when
working with large accounts.

To use zero-copy add the `bytemuck` crate to your dependencies. Add the
`min_const_generics` feature to allow working with arrays of any size in your
zero-copy types.

```toml title="Cargo.toml"
[dependencies]
bytemuck = { version = "1.20.0", features = ["min_const_generics"] }
anchor-lang = "0.31.1"
```

### Define a Zero Copy Account

To define an account type that uses zero-copy, annotate the struct with
[`#[account(zero_copy)]`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/account/src/lib.rs#L417).

```rust
// [!code highlight]
#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10232],
}
```

The `#[account(zero_copy)]` attribute automatically implements several traits
required for zero-copy deserialization:

```rust
// [!code highlight:4]
#[derive(Copy, Clone)]
#[derive(bytemuck::Zeroable)]
#[derive(bytemuck::Pod)]
#[repr(C)]
struct Data {
  // --snip--
}
```

### Use AccountLoader for Zero Copy Accounts

To deserialize a zero-copy account, use
[`AccountLoader<'info, T>`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L95-L99),
where `T` is the zero-copy account type defined with the `#[account(zero_copy)]`
attribute.

For example:

```rust
#[derive(Accounts)]
pub struct InstructionAccounts<'info> {
    // [!code word:AccountLoader]
    // [!code highlight]
    pub zero_copy_account: AccountLoader<'info, Data>,
}
```

#### Initialize a Zero Copy Account

The `init` constraint can be used with the `AccountLoader` type to create a
zero-copy account.

```rust
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        // [!code word:init:1]
        // [!code highlight:4]
        init,
        // 10240 bytes is max space to allocate with init constraint
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
```

<Callout type="info">
  The `init` constraint is limited to allocating a maximum of 10240 bytes due to
  CPI limitations. Under the hood, the `init` constraint makes a CPI call to the
  SystemProgram to create the account.
</Callout>

When initializing a zero-copy account for the first time, use
[`load_init`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L199-L221)
to get a mutable reference to the account data. The `load_init` method also sets
the account discriminator.

```rust
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    // [!code word:load_init]
    // [!code highlight]
    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10232];
    Ok(())
}
```

For accounts that require more than 10240 bytes, use the
[`zero`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/syn/src/codegen/accounts/constraints.rs#L200-L264)
constraint instead of `init`. The `zero` constraint verifies the account has not
been initialized by checking that its discriminator has not been set.

```rust
#[derive(Accounts)]
pub struct Initialize<'info> {
    // [!code word:zero]
    // [!code highlight]
    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}
```

With the `zero` constraint, you'll need to first create the account in a
separate instruction by directly calling the System Program. This allows you to
create accounts up to Solana's maximum account size of 10MB (10_485_760 bytes),
bypassing the CPI limitation.

Just as before, use `load_init` to get a mutable reference to the account data
and set the account discriminator. Since 8 bytes are reserved for the account
discriminator, the maximum data size is 10_485_752 bytes (10MB - 8 bytes).

```rust
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    // [!code word:load_init]
    // [!code highlight]
    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10_485_752];
    Ok(())
}
```

#### Update a Zero Copy Account

Use
[`load_mut`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L172-L195)
when you need mutable access to update an existing zero-copy account:

```rust
#[derive(Accounts)]
pub struct Update<'info> {
    // [!code highlight]
    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}
```

```rust
pub fn update(ctx: Context<Update>) -> Result<()> {
    // [!code word:load_mut]
    // [!code highlight]
    let account = &mut ctx.accounts.data_account.load_mut()?;
    account.data = [2; 10232];
    Ok(())
}
```

#### Read a Zero Copy Account

Use
[`load`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L154-L169)
to only read the account data.

```rust
#[derive(Accounts)]
pub struct ReadOnly<'info> {
    pub data_account: AccountLoader<'info, Data>,
}
```

```rust
pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {
    // [!code word:load]
    // [!code highlight]
    let account = &ctx.accounts.data_account.load()?;
    msg!("First 10 bytes: {:?}", &account.data[..10]);
    Ok(())
}
```

## Examples

The examples below demonstrate two approaches for initializing zero-copy
accounts in Anchor:

1. Using the `init` constraint to initialize the account in a single instruction
2. Using the `zero` constraint to initialize an account with data greater than
   10240 bytes

### Zero Copy

<Tabs items={["Program", "Client"]}>
<Tab value="Program">

```rust title="lib.rs"
use anchor_lang::prelude::*;

declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1");

#[program]
pub mod zero_copy {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10232];
        Ok(())
    }

    pub fn update(ctx: Context<Update>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_mut()?;
        account.data = [2; 10232];
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        // 10240 bytes is max space to allocate with init constraint
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}

#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10232],
}
```

</Tab>
<Tab value="Client">

```ts title="test.ts"
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ZeroCopy } from "../target/types/zero_copy";

describe("zero-copy", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.ZeroCopy as Program<ZeroCopy>;
  const dataAccount = anchor.web3.Keypair.generate();

  it("Is initialized!", async () => {
    const tx = await program.methods
      .initialize()
      .accounts({
        dataAccount: dataAccount.publicKey,
      })
      .signers([dataAccount])
      .rpc();
    console.log("Your transaction signature", tx);

    const account = await program.account.data.fetch(dataAccount.publicKey);
    console.log("Account", account);
  });

  it("Update!", async () => {
    const tx = await program.methods
      .update()
      .accounts({
        dataAccount: dataAccount.publicKey,
      })
      .rpc();
    console.log("Your transaction signature", tx);

    const account = await program.account.data.fetch(dataAccount.publicKey);
    console.log("Account", account);
  });
});
```

</Tab>
</Tabs>

### Initialize Large Account

When initializing an account that requires more than 10,240 bytes of space, you
must split the initialization into two steps:

1. Create the account in a separate instruction invoking the System Program
2. Initialize the account data in your program instruction

Note that the maximum Solana account size is 10MB (10_485_760 bytes), 8 bytes
are reserved for the account discriminator.

<Tabs items={["Program", "Client"]}>
<Tab value="Program">

```rust title="lib.rs"
use anchor_lang::prelude::*;

declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH");

#[program]
pub mod zero_copy_two {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10_485_752];
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}

#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10_485_752],
}
```

</Tab>
<Tab value="Client">

```ts title="test.ts"
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ZeroCopyTwo } from "../target/types/zero_copy_two";

describe("zero-copy-two", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.ZeroCopyTwo as Program<ZeroCopyTwo>;
  const dataAccount = anchor.web3.Keypair.generate();

  it("Is initialized!", async () => {
    const space = 10_485_760; // 10MB max account size
    const lamports =
      await program.provider.connection.getMinimumBalanceForRentExemption(
        space,
      );

    // [!code highlight:7]
    const createAccountInstruction = anchor.web3.SystemProgram.createAccount({
      fromPubkey: program.provider.publicKey,
      newAccountPubkey: dataAccount.publicKey,
      space,
      lamports,
      programId: program.programId,
    });

    // [!code highlight:6]
    const initializeInstruction = await program.methods
      .initialize()
      .accounts({
        dataAccount: dataAccount.publicKey,
      })
      .instruction();

    const transaction = new anchor.web3.Transaction().add(
      createAccountInstruction,
      initializeInstruction,
    );

    const tx = await program.provider.sendAndConfirm(transaction, [
      dataAccount,
    ]);

    console.log("Your transaction signature", tx);

    const account = await program.account.data.fetch(dataAccount.publicKey);
    console.log("Account", account);
  });
});
```

</Tab>
</Tabs>
